#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Native Client SDK with Bionic
"""

import argparse
import fnmatch
import os
import stat
import shutil
import StringIO
import sys

sys.path.append('tools')

import process
from bionic_dirs import *
from cmake_tools import ConfigureCMake
from file_update import Mkdir, Rmdir, Symlink
from file_update import NeedsUpdate, UpdateFromTo, UpdateText
from make_tools import ConfigureProject, ConfigureThirdParty, MakeProject
from replace import ReplaceArch, ReplaceText, ReplaceList

DEFAULT_CPU_TYPES = ['arm', 'x86_64']
DEFAULT_ARCH_TYPES = ['arm', 'i686', 'x86_64']

def BuildPNaCl(options):
  tc_args = ['python', 'toolchain_build_pnacl.py',
             '--install', os.path.join(OUTPUT_ROOT, 'bionic_work')]

  #if options.no_cache:
  tc_args += ['--no-use-cached-results', '--no-use-remote-cache']

  print 'Building PNaCl: ' + ' '.join(tc_args)
  process.Run(tc_args, cwd=TOOLCHAIN_BUILD_DIR, outfile=sys.stdout)


def BuildSCons(options):
  scons_args = ['./scons', '--mode=nacl,dbg-linux', '-j20']
  for arch in ['arm', 'x86_64', 'x86_32']:
    platform = 'platform=' + arch
    irt = 'scons-out/nacl_irt-%s/staging/irt_core.nexe' % arch.replace('_', '-')
    bootstrap = 'nacl_helper_bootstrap'
    print 'Building SCons: ' + arch
    process.Run(scons_args + [platform, 'sel_ldr', irt, bootstrap],
              cwd=NATIVE_CLIENT_DIR, outfile=sys.stdout)


def UpdateSCons(options):
  print 'Updating SCons components.'
  for arch in ['arm', 'i686', 'x86_64']:
    scons_path = os.path.join(NATIVE_CLIENT_DIR, 'scons-out', 'dbg-linux-$SCON',
                              'staging')
    scons_path = ReplaceArch(scons_path, arch)

    for exe in ['nacl_helper_bootstrap', 'sel_ldr']:
      src = os.path.join(scons_path, exe)
      dst = os.path.join(OUTPUT_ROOT, 'tools', arch, exe)
      UpdateFromTo(src,dst)

    src = os.path.join(NATIVE_CLIENT_DIR, 'scons-out', 'nacl_irt-$SCON', 
                       'staging', 'irt_core.nexe')
    src = ReplaceArch(src, arch)
    dst = os.path.join(OUTPUT_ROOT, 'tools', arch, 'irt_core.nexe')
    UpdateFromTo(src, dst)


def ReplaceArmTool(tool):
  EXEC= '#!/bin/sh\nexec `dirname $0`/' + tool
  EXEC+=' --target=armv7a-none-nacl-gnueabihf"$@"'
  dst = os.path.join(OUTPUT_ROOT, 'bionic_install', 'bin', 'arm-nacl-' + tool)
  with open(dst, 'w') as dstf:
    dstf.write(EXEC)
  mod = stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH
  os.chmod(dst, mod)

# Shared = *.xsc
# Dynamic = *.xs --Ttext-sections=0x100000
# Static = *.xs
def UpdateBionicToolchain(arches):
  print 'Updating Bionic Toolchain.'
  COPY_FULL = [
    'arm_bc-nacl', 'bin', 'docs',
    'i686_bc-nacl', 'include', 'le32-nacl',
    'share', 'target_lib_compiler',
    'translator', 'x86_64_bc-nacl',
    'FEATURE_VERSION', 'README', 'REV',
    'lib',
    'arm-nacl/bin', 
    'arm-nacl/lib/ldscripts', 
    'arm-nacl/include/c++',
    'x86_64-nacl/bin',
    'x86_64-nacl/lib/ldscripts',
    'x86_64-nacl/include/c++',
  ]
  BOGUS = [
    'lib/clang/3.4/include',
    'lib/clang/3.4/lib/arm_bc-nacl',
    'lib/clang/3.4/lib/i686_bc-nacl',
    'lib/clang/3.4/lib/le32-nacl',
    'lib/clang/3.4/lib/x86_64-nacl',
  ]

  srcpath = os.path.join(OUTPUT_ROOT, 'bionic_work')
  dstpath = os.path.join(OUTPUT_ROOT, 'bionic_install')
  for dirpath in COPY_FULL:
    UpdateFromTo(os.path.join(srcpath, dirpath), os.path.join(dstpath, dirpath))

  EMPTY = """/*
 * This is a dummy linker script.
 * libnacl.a, libcrt_common.a, crt0.o crt1.o crti.o and crtn.o are all
 * empty.  Instead all the work is done by crtbegin(S).o and crtend(S).o and
 * the bionic libc.  These are provided for compatability with the newlib
 * toolchain binaries.
 */"""
  EMPTY_FILES = ['crt0.o', 'crt1.o', 'crti.o', 'crtn.o',
                 'libnacl.a', 'libcrt_common.a', 'libpthread.a']

  for arch in arches:
    # Create empty objects and libraries
    libpath = ReplaceArch(os.path.join(dstpath, '$LIB'), arch)
    for name in EMPTY_FILES:
      UpdateText(os.path.join(libpath, name), EMPTY)

  # Copy Assembly Macro Files
  src = os.path.join(NATIVE_CLIENT_DIR, 'pnacl', 'support', 'clang_direct',
                     'nacl-arm-macros.s')
  dst = os.path.join(OUTPUT_ROOT, 'bionic_install', 'arm-nacl', 'lib',
                     'nacl-arm-macros.s')
  UpdateFromTo(src, dst) 


def MungeIRT(src, dst):
  replace_map = {
    'off_t': 'int64_t',
    'native_client/src/untrusted/irt/' : '',
  }

  if not NeedsUpdate(src,dst):
    return

  print 'Munging IRT %s -> %s.' % (src,dst)
  with open(src, 'r') as srcf:
    text = srcf.read()
    text = ReplaceText(text, [replace_map])
    with open(dst, 'w') as dstf:
      dstf.write(text)


def UpdateBionicHeaders(cpus):
  BIONIC_PAIRS = [
    ('libc/arch-nacl/syscalls/irt_poll.h', '$INC/irt_poll.h'),
    ('libc/arch-nacl/syscalls/irt_socket.h', '$INC/irt_socket.h'),
    ('libc/include', '$INC'),
    ('libc/arch-nacl/syscalls/nacl_socket.h', '$INC/nacl_socket.h'),
    ('libc/arch-nacl/syscalls/nacl_stat.h', '$INC/nacl_stat.h'),
    ('libc/arch-pnacl/include/machine',
        '$INC/machine'),
    ('libc/arch-$MACH/include/machine',
        '$INC/machine'),
    ('libc/kernel/common', '$INC'),
    ('libc/kernel/arch-$CPU/asm', '$INC/asm'),
    # Math Includes
    ('libm/include', '$INC'),
    ('libm/$MATH', '$INC'),
    # Misc
    ('safe-iop/include', '$INC'),
    ('nacl/$INC', '$INC'),
  ]
  GCC_PAIRS = [
#   ('ucontext.h', '$INC/ucontext.h'),
#   ('sys/ucontext.h', '$INC/sys/ucontext.h'),
#   ('bits/sigcontext.h', '$INC/bits/sigcontext.h'),
#   ('bits/wordsize.h', '$INC/bits/wordsize.h')
  ]
  for arch in cpus:
    install_path = os.path.join(OUTPUT_ROOT, 'bionic_install')

    Mkdir(install_path)
    for src, dst in BIONIC_PAIRS:
      srcpath = ReplaceArch(os.path.join(BIONIC_SRC, src), arch)
      dstpath = ReplaceArch(os.path.join(install_path, dst), arch)
      UpdateFromTo(srcpath, dstpath, filters=['*.S', '*.inc'])

    for name in ['irt.h', 'irt_dev.h']:
      src = os.path.join(NATIVE_CLIENT_DIR, 'src', 'untrusted', 'irt', name)
      dst = os.path.join(install_path, '$INC')
      dst = ReplaceArch(dst, arch)
      MungeIRT(src, os.path.join(dst, name))

    for src, dst in GCC_PAIRS:
      srcpath = ReplaceArch(os.path.join(TOOLCHAIN_DIR, 'linux_$CPU', 
                                         'nacl_$CPU_glibc', '$NACL', 
                                         'include', src), arch)
      dstpath = ReplaceArch(os.path.join(install_path, dst), arch)
      UpdateFromTo(srcpath, dstpath)


def main(argv):
  parser = argparse.ArgumentParser(add_help=False)
  parser.add_argument(
      '-v', '--verbose', dest='verbose',
      default=False, action='store_true',
      help='Produce more output.')

  parser.add_argument(
      '-a', '--arch', dest='arches',
      default=[], action='append',
      help='Add arch to buildset (non means build all).')

  parser.add_argument(
      '-b', '--rebuild-bionic', dest='rebuild_bionic',
      default=False, action='store_true',
      help='Produce more output.')

  parser.add_argument(
      '-c', '--clobber', dest='clobber',
      default=False, action='store_true',
      help='Clobber working directories before building.')

  parser.add_argument(
      '-p', '--rebuild-pnacl', dest='rebuild_pnacl',
      default=False, action='store_true',
      help='Rebuild pnacl step.')

  parser.add_argument(
      '-s', '--rebuild-scons', dest='rebuild_scons',
      default=False, action='store_true',
      help='Rebuild sel_ldr, irt, and bootstrap.')

  parser.add_argument(
      '-t', '--clobber-toolchain', dest='clobber_toolchain',
      default=False, action='store_true',
      help='Clobber toolchain directory.')

  parser.add_argument(
      '--update-only', dest='update_only',
      default=False, action='store_true',
      help='Only update files.')

  options, leftover_args = parser.parse_known_args(argv)
  if '-h' in leftover_args or '--help' in leftover_args:
    parser.print_help()
    return 1

  if not options.arches:
    options.arches = DEFAULT_ARCH_TYPES
    
  for arch in options.arches:
    if arch not in DEFAULT_ARCH_TYPES:
      print 'ARCH %s unrecognized.' % arch
      print 'Expecting: ' + ' '.join(DEFAULT_ARCH_TYPES)
      return 1

  if options.clobber:
    print 'Re-prep this checkout.'
    process.Run(['python', 'prep.py', '-c'])

    # No need to rebuild after we already built during prep
    options.rebuild_pnacl = False
    options.rebuild_scons = False

  if options.rebuild_pnacl:
    BuildPNaCl(options)

  if options.rebuild_scons:
    BuildSCons(options)
  UpdateSCons(options)

  if options.clobber_toolchain:
    Rmdir(os.path.join(OUTPUT_ROOT, 'bionic_install'))

  UpdateBionicToolchain(options.arches)
  UpdateBionicHeaders(DEFAULT_CPU_TYPES)

  if options.update_only:
    return 0

  for arch in options.arches:
    ConfigureProject(arch, 'irt', options.rebuild_bionic)
    ConfigureProject(arch, 'libc', options.rebuild_bionic)
    ConfigureProject(arch, 'libgcc_eh', options.rebuild_bionic)
    ConfigureProject(arch, 'libm', options.rebuild_bionic)
    ConfigureProject(arch, 'linker', options.rebuild_bionic)
    ConfigureProject(arch, 'tests', options.rebuild_bionic)

  for arch in options.arches:
    MakeProject(arch, 'irt', jobs=1)
    MakeProject(arch, 'libc')
    MakeProject(arch, 'libgcc_eh')
    MakeProject(arch, 'libm')
    MakeProject(arch, 'linker')

    host = arch + '-nacl'
    libunwind_args = ['--enable-setjmp', '--disable-cxx-exceptions',
                      '--enable-static', '--enable-shared',
                      '--disable-coredump', '--disable-ptrace',
                      '--disable-debug', 
                      '--disable-debug-frame', '--disable-block-signales',
                      '--disable-conservative-checks', '--disable-msabi-support',
                      '--disable-minidebuginfo',
                      'ac_cv_func_dl_iterate_phdr=yes',
                      '--host=' + host, '--target=' + host, '--build=i686']
    # ConfigureThirdParty(arch, 'libunwind', libunwind_args)
    # MakeProject(arch, 'libunwind')


  for arch in options.arches:
    MakeProject(arch, 'tests')
    MakeProject(arch, 'tests', ['static_tests'])
    MakeProject(arch, 'tests', ['dynamic_tests'])
    # MakeProject(arch, 'libunwind', ['src/.libs/libunwind.a'])
  
  return 0

if __name__ == '__main__':
  sys.exit(main(sys.argv))

